'''The base SimpleCells table model.
'''

from PyQt4.QtCore import Qt, QAbstractTableModel, QModelIndex, QVariant, SIGNAL,QString, \
    QCoreApplication
from PyQt4.QtGui import QFont, QColor, qRgb
from SimpleCells.Components.Table.SCDataAtom import SCDataAtom, SCRecordError, SCDataAtomHelper
from SimpleCells.Components.Table.SCTableData import SCTableData
from SimpleCells.Components.Table.SCTableViewTools import SCTableReadTools,\
    SCTableWriteTools

DATA_COLORS = {'label'  : (qRgb(194,194,194),qRgb(0,34,102)), 
               'date'   : qRgb(185,211,238),
               'number' : qRgb(244,164,96),
               'float'  : qRgb(244,164,96),
               'int'    : qRgb(175,238,238),
               'string' : qRgb(152,251,152),
               'bool'   : qRgb(219,219,112),
               'other'  : qRgb(100,100,200),
               'expr'   : qRgb(255,204,204),
               'button' : (qRgb(112,128,144),qRgb(255,255,255)),
               'locked' : qRgb(255,0,0)}

DATA_FONTS = {'label': QFont("Sans Serif",8,QFont.Bold),
              'akaLabel': QFont("Sans Serif", 8, QFont.Bold, True)}

def simplify(aFunc):
    def new(cls,data):
        return SCTableReadTools.simplifyShape(aFunc(cls,data),0)
    return new

class SCTableModel(QAbstractTableModel):
    '''The base table model for SCTableView.
    '''
    
    CONVERTERS = {'float' : float, 'int' : int}
    
    def __init__(self, nRows = 100, nCols = 20, parent = None, optWS = {}):
        '''
        @param nRows, the number of table rows
        @param nCols, the number of table cols
        @param parent, the class parent
        '''
        QAbstractTableModel.__init__(self,parent)
        self.__data = SCTableData(nRows,nCols)
        self.__labelIdx = {}
        self.__atomHelper = SCDataAtomHelper(optWS)

    def convertCell(self,index,toWhat):
        ''''''
        converter = SCTableModel.CONVERTERS[toWhat]
        try:
            data = self.__data[index].data 
            if isinstance(data,basestring):
                data = data.replace(',','')
                data = data.replace('%','/100.')
                data = eval(data)
            value = converter(data)
            self.setData(index,value)
        except:
            print 'Cannot convert to %s.' % toWhat

    def setTableData(self, tableData, labels = {}):
        self.__data = tableData
        self.setLabels(labels)
        self.reset()
    
    def setWorkspace(self,optWS):
        self.__atomHelper.setWorkspace(optWS)
        
    def getTableData(self):
        return self.__data

    def evalCell(self,row,col):
        return self.__atomHelper.evalAtom(self.__data(row,col))
    
    def getCell(self,row,col):
        return self.__data(row,col)
    
    def setCell(self,row,col,value):
        self.setData(self.index(row,col),value)

    def labelShapeHint(self,label):
        assert self.hasLabel(label), 'Label not defined.'
        row, col = self.__labelIdx[label]            
        return self.__data(row,col).shapeHint
        
    def labelAka(self,label,rec=True,count=0):
        assert count < 11, 'Maximum recursion reached in reading akaLabels.' 
        assert self.hasLabel(label), 'Label not defined' 
        row, col = self.__labelIdx[label]
        akaLabel = self.__data(row,col).akaName
        if akaLabel and rec: return self.labelAka(akaLabel,count=count+1)
        if akaLabel: return akaLabel
        return label

    def labels(self):
        return self.__labelIdx
    
    def delLabel(self, label):
        row, col = self.__labelIdx[label]
        self.setCell(row,col,None)
        
    def setLabels(self, labels):
        self.__labelIdx = labels

    def getLabelIndex(self,label):
        assert self.hasLabel(label), 'Label not defined.'
        row, col = self.__labelIdx[label]
        return self.index(row,col,QModelIndex())
        
    def resetLabels(self):
        self.__labelIdx.clear()
    
    def hasLabel(self, label):
        return self.__labelIdx.has_key(str(label)) 
        
    def rowCount(self, parent=QModelIndex()):
        return self.__data.nRows()
    
    def columnCount(self, parent=QModelIndex()):
        return self.__data.nCols()

    def headerData(self,section,orientation,role = Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return QVariant(int(section))
        return QVariant()

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()
        if role == Qt.UserRole:
            return self.__atomHelper.atomToQVariant(self.__data[index],truncFloat=True)
        if role == Qt.DisplayRole:
            if self.isLocked(index):
                variant = self.__atomHelper.atomToQVariant(self.__data[index],truncFloat=True)
                return QVariant(QString('[ %1 ]').arg(variant.toString()))
            return self.__atomHelper.atomToQVariant(self.__data[index],truncFloat=True)
        elif role in (Qt.EditRole, Qt.ToolTipRole):
            return QVariant(self.__atomHelper.atomToQString(self.__data[index]))
        elif role == Qt.TextAlignmentRole:
            if self.__data[index].imLabel():
                return QVariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
            return QVariant(int(Qt.AlignRight|Qt.AlignVCenter))
        elif role == Qt.FontRole:
            if self.__data[index].imAKA():
                return QVariant(DATA_FONTS['akaLabel'])
            elif self.__data[index].imLabel():
                return QVariant(DATA_FONTS['label'])
        elif role == Qt.ForegroundRole:
            if self.__data[index].imLabel():
                return QVariant(QColor(DATA_COLORS['label'][1]))
        elif role == Qt.BackgroundRole:
            if self.__data[index].imLabel():
                return QVariant(QColor(DATA_COLORS['label'][0]))
            elif self.__data[index].imDate():
                return QVariant(QColor(DATA_COLORS['date']))
            elif self.__data[index].imInt():
                return QVariant(QColor(DATA_COLORS['int']))            
            elif self.__data[index].imFloat():
                return QVariant(QColor(DATA_COLORS['float']))
            elif self.__data[index].imExpr():
                return QVariant(QColor(DATA_COLORS['expr']))            
            elif self.__data[index].imString():
                return QVariant(QColor(DATA_COLORS['string']))
            elif self.__data[index].imBool():
                return QVariant(QColor(DATA_COLORS['bool']))
        return QVariant()

    def recordData(self, index, value):
        if self.isLocked(index):
            return False
        try:
            # Init Settings
            if self.__data[index].imLabel():
                self.__delLabelAtIndex__(index)
            # Record data
            if isinstance(value,QVariant):
                self.__data[index] = self.__atomHelper.atomFromQVariant(value)
            else:
                self.__data[index] = SCDataAtom(value)
            # Final Settings
            if self.__data[index].imLabel():
                try:
                    self.__addLabel__(index, self.__data[index].data)
                except KeyError:
                    self.__data[index] = SCDataAtom()
                    return False
            return True
        except SCRecordError:
            return False

    def setData(self, index, value, role=Qt.EditRole):
        if not index.isValid():
            return False
        if role == Qt.EditRole and self.recordData(index,value):
            self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),index,index)
            QCoreApplication.processEvents()
            return True
        return False
    
    def flags(self, index):
        if index.isValid():
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index)|\
                                Qt.ItemIsEditable)

    def lockData(self,index):
        if index.isValid():
            self.__data[index].locked = True
            self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),index,index)
    
    def unlockData(self,index):
        if index.isValid():
            self.__data[index].locked = False
            self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),index,index)

    def clearAll(self):
        self.resetAll(self.__data.nRows(),self.__data.nCols())
    
    def resetAll(self,rows,cols):
        self.__data = SCTableData(rows,cols)
        self.__labelIdx = {}
        self.reset()
    
    def __addLabel__(self, index, label):
        if self.__labelIdx.has_key(label):
            raise KeyError("Label already defined.")
        self.__labelIdx[label] = (index.row(),index.column())
        self.emit(SIGNAL('labelAdded(QModelIndex,QString)'),index,QString(label))

    def __delLabel__(self, label):
        self.__labelIdx.pop(label)
        self.emit(SIGNAL('labelDeleted(QString)'),QString(label))
        
    def __delLabelAtIndex__(self,index):
        label = self.__data[index].data
        self.__delLabel__(label)
    
    # Writing data
    def __checkWriteData__(self, label, data):
        index = self.index(*self.__labelIdx[str(label)])
        startIndex = None
        if self.__data[index].imLabel('horiz'):
            startIndex = self.index(index.row(),index.column()+1)
        else:
            startIndex = self.index(index.row()+1,index.column())
        SCTableWriteTools.checkSpace(data, self, startIndex, self.__data[index].direction)

    def isLocked(self,index):
        try:
            return self.__data[index].locked
        except AttributeError:
            return False

    def writeDataToLabel(self, label, data):
        ''''''
        if not self.hasLabel(label):
            raise KeyError("Label '%s' has not been defined." % str(label))
        self.__checkWriteData__(label, data)
        self.clearLabelData(label)
        index = self.index(*self.__labelIdx[str(label)])
        if self.__data[index].imLabel('vert'):
            self.__writeVertDataToIndex__(index,data)
        elif self.__data[index].imLabel('horiz'):
            self.__writeHorizDataToIndex__(index,data)
        elif self.__data[index].imLabel('both'):
            self.__writeBothDataToIndex__(index,data)
    
    def __writeVertDataToIndex__(self, index, data):
        ''''''
        if not hasattr(data,'__iter__'):
            data = [data,]
        for i, row in enumerate(range(index.row()+1,index.row()+len(data)+1)):
            idx = self.index(row,index.column())
            if not idx.isValid(): break
            if self.isLocked(idx): continue
            if (not self.__data[idx].imNone()): break
            self.setData(idx,data[i])
            #self.setData(idx,self.__atomHelper.atomToQVariant(SCDataAtom(data[i])))

    def __writeHorizDataToIndex__(self, index, data):
        ''''''
        if not hasattr(data,'__iter__'):
            data = [data,]
        for i, col in enumerate(range(index.column()+1,index.column()+len(data)+1)):
            idx = self.index(index.row(),col)
            if not idx.isValid(): break
            if self.isLocked(idx): continue
            if (not self.__data[idx].imNone()): break
            self.setData(idx,data[i])
            #self.setData(idx,self.__atomHelper.atomToQVariant(SCDataAtom(data[i])))
    
    def __writeBothDataToIndex__(self, index, data):
        ''''''
        if not hasattr(data,'__iter__'):
            data = [data,]
        if isinstance(data,tuple):
            data = list(data)
        for i, row in enumerate(range(index.row()+1,index.row()+len(data)+1)):
            tmpIdx = self.index(row,index.column())
            if not tmpIdx.isValid(): break
            if (not self.__data[tmpIdx].imNone() and \
                not self.isLocked(tmpIdx)): break
            if not hasattr(data[i],'__iter__'):
                data[i] = [data[i],]
            for j, col in enumerate(range(index.column(),index.column()+len(data[i]))):
                idx = self.index(row,col)
                if not idx.isValid(): break
                if self.isLocked(idx): continue
                if (not self.__data[idx].imNone()): break
                self.setData(idx,data[i][j])
                #self.setData(idx,self.__atomHelper.atomToQVariant(SCDataAtom(data[i][j])))
    
    # Reading data
    #@simplify 
    def readDataFromLabel(self, label):
        '''Return data associated to a label.
        '''
        if not self.hasLabel(label):
            raise KeyError("Label '%s' has not been defined." % str(label))
        index = self.index(*self.__labelIdx[str(label)])
        if self.__data[index].imLabel('vert'):
            return self.__readVertDataFromIndex__(index)
        elif self.__data[index].imLabel('horiz'):
            return self.__readHorizDataFromIndex__(index)
        elif self.__data[index].imLabel('both'):
            return self.__readBothDataFromIndex__(index)
        return None
        
    def __readVertDataFromIndex__(self, index):
        '''Read from a label vertically until a None or a label 
        is found.
        '''
        out = []
        for row in range(index.row()+1,self.rowCount()):
            idx = self.index(row,index.column()) 
            if (self.__data[idx].imNone() or \
                self.__data[idx].imLabel()): break
            out.append(self.__atomHelper.evalAtom(self.__data[idx]))
        return out
    
    def __readHorizDataFromIndex__(self, index):
        '''Read from a label horizontally until a None or a label
        is found.
        '''
        out = []
        for col in range(index.column()+1,self.columnCount()):
            idx = self.index(index.row(),col)
            if ( self.__data[idx].imNone() or \
                 self.__data[idx].imLabel()): break
            out.append(self.__atomHelper.evalAtom(self.__data[idx]))
        return out
    
    def __readBothDataFromIndex__(self, index):
        '''Read from a label in 2d until a None or a label is found.
        '''
        out = []
        for row in range(index.row()+1,self.rowCount()):
            tmpIdx = self.index(row,index.column())
            if (self.__data[tmpIdx].imNone() or \
                 self.__data[tmpIdx].imLabel()): break
            tmpOut = []
            for col in range(index.column(),self.columnCount()):
                idx = self.index(row,col)
                if ( self.__data[idx].imNone() or \
                     self.__data[idx].imLabel()): break
                tmpOut.append(self.__atomHelper.evalAtom(self.__data[idx]))
            out.append(tmpOut)
        return out

    # Clearing data
    
    def clearAllLabelData(self):
        ''''''
        for label in self.__labelIdx.keys():
            self.clearLabelData(label)

    def clearLabelData(self, label):
        ''''''
        if not self.hasLabel(label):
            raise KeyError("Label '%s' has not been defined." % str(label))
        index = self.index(*self.__labelIdx[str(label)])
        if self.__data[index].imLabel('vert'):
            self.__clearVertDataFromIndex__(index)
        elif self.__data[index].imLabel('horiz'):
            self.__clearHorizDataFromIndex__(index)
        elif self.__data[index].imLabel('both'):
            self.__clearBothDataFromIndex__(index)

    def __clearVertDataFromIndex__(self, index):
        ''''''
        for row in range(index.row()+1,self.rowCount()):
            idx = self.index(row,index.column())
            if not idx.isValid(): break
            if (self.__data[idx].imNone() or \
                self.__data[idx].imLabel()): break
            self.setData(idx,QVariant())
    
    def __clearHorizDataFromIndex__(self, index):
        ''''''
        for col in range(index.column()+1,self.columnCount()):
            idx = self.index(index.row(),col)
            if not idx.isValid(): break
            if (self.__data[idx].imNone() or \
                self.__data[idx].imLabel()): break
            self.setData(idx,QVariant())
    
    def __clearBothDataFromIndex__(self, index):
        ''''''
        for row in range(index.row()+1,self.rowCount()):
            tmpIdx = self.index(row,index.column())
            if (self.__data[tmpIdx].imNone() or \
                self.__data[tmpIdx].imLabel()): break
            for col in range(index.column(),self.columnCount()):
                idx = self.index(row,col)
                if ( self.__data[idx].imNone() or \
                     self.__data[idx].imLabel()): break
                self.setData(idx,QVariant())

    def invertedLabels(self):
        return dict((self.__labelIdx[k],k) for k in self.__labelIdx)

    def findLabels(self,indexList):
        '''Returns labels in index list'''
        invLabels = self.invertedLabels()
        labels = []
        for index in indexList:
            if invLabels.has_key((index.row(),index.column())):
                labels.append(invLabels.get((index.row(),index.column())))
        return labels
    
    def findLabelsInRange(self,row,rowCount,col,colCount):
        invLabels = self.invertedLabels()
        labels = []
        for r in range(row,row+rowCount):
            for c in range(col,col+colCount):
                if invLabels.has_key((r,c)):
                    labels.append(invLabels.get((r,c)))
        return labels
    
    def findLabelsBelow(self,row,col):
        invLabels = self.invertedLabels()
        labels = []
        for r in range(row,self.rowCount()):
            for c in range(col,self.columnCount()):
                if invLabels.has_key((r,c)):
                    labels.append(invLabels.get((r,c)))
        return labels

    def __shiftLabels__(self,labels,rowShift,colShift):
        '''Shifts given labels by given amount'''
        for label in labels:
            tmp = self.__labelIdx[label]
            self.__labelIdx[label] = (tmp[0]+rowShift,tmp[-1]+colShift)

    def removeRows(self,row,count,parent=QModelIndex()):
        # Remove labels
        delLabels = self.findLabelsInRange(row, count+1, 0, self.columnCount())
        map(self.__delLabel__,delLabels)
        # Removing data
        self.beginRemoveRows(parent,row,row+count)
        for _ in range(row,row+count+1):
            self.__data.delRow(row)
        self.endRemoveRows()
        # Shift labels
        shiftedLabels = self.findLabelsBelow(row+count+1, 0)
        self.__shiftLabels__(shiftedLabels, -count-1, 0)
        return True
    
    def removeColumns(self,column,count,parent=QModelIndex()):
        # Remove labels 
        delLabels = self.findLabelsInRange(0, self.rowCount(parent), column, count+1)
        map(self.__delLabel__,delLabels)
        # Removing data
        self.beginRemoveColumns(parent,column,column+count)
        for _ in range(column,column+count+1):
            self.__data.delCol(column)
        self.endRemoveColumns()
        # Shift labels
        shiftedLabels = self.findLabelsBelow(0, column+count+1)
        self.__shiftLabels__(shiftedLabels, 0, -count-1)
        return True
    
    def addRows(self,rows):
        self.__data.addRows(rows)
        self.reset()
    
    def addColumns(self,cols):
        self.__data.addCols(cols)
        self.reset()

if __name__ == '__main__':
    pass


